#include <stdio.h>
#include <reg52.h>
#include <intrins.h> 
#include <math.h>
#include <string.h>

typedef unsigned char u8;
typedef unsigned int u16;

sbit P3_0 = P3^0;
sbit P3_1 = P3^1;
sbit P3_2 = P3^2;
sbit P3_3 = P3^3;
sbit DC_Motor = P1;//电机控制芯片输入引脚，其实是前四个口，但是我为方便，就直接P1了
sbit lcdRS = P3^5;//命令、数据寄存器选择
sbit lcdRW = P3^6;//读写选择
sbit lcdEN = P3^4;//使能

u16 percentage = 50;
u16 iterator;//迭代器
u16 copies = 40;//份数，总数为100，iterator为50就是50%

u8 table[] = "Duty Ratio:";
u8 string[] = "          ";
/*
关于宏定义：
1、\为宏定义换行符
2、可以带参数宏定义
3、##为参数拼接符号：TH##n//当n=1时，TH1
4、#为转字符串符号：#define abc(x) #x//abc(1):"1"、abc(a)//当a为一变量a=1，也是"a",而不是"1"
*/

//(Hi*2^4) | Lo 按位或后赋值给target
#define MakeByte(target, Hi,Lo) \  
    do{ \  
        target |= (((Hi) << 4)|(Lo)); \   
    }while(0); \  
      
#define SetTH(n,val) \  
    do{ \  
        TH##n = val; \  
    }while(0); \  
      
#define SetTL(n,val)  \  
    do{ \  
        TL##n = val; \  
    }while(0); \  
      
//定时器0允许开关：ET0 = 1；    
//中断开放寄存器
#define EnableET(n) \  
    do{ \  
        ET##n = 0x01; \  
        IE |= 0x80; \    
    }while(0); \ 
 
void DelayMs(u16 ms)
{
	int i=0,j=0;
	for(;i<ms;i++)
	{
		for(j=0;j<998;j++)
		{
			_nop_();
		}
	}
}

enum KEYSTAT
{
	KEYDOWN=0,KEYUP=1
};//也可以省略序号赋值，默认从0开始赋值标号


/*
此函数控制占空比
*/
void OnKeyDown_0()
    { 
    	if(iterator >= copies)//占空比拉满还加就给置位到0%
    	{
            iterator = 0;//随然到头了我还给初始化，但是我还是得让电机停下来
            DC_Motor = 0;//ULN2003输入0，输出1，电机两电极都是1，不转
        }	
    	else
    		iterator += 5;//按一次加一次，一次加5%
    }
     

void OnKeyDown_1()
{
    DC_Motor = 0;//电机停止
    iterator = 0;
}

void OnKeyDown_2()
{
    DC_Motor = 1;//电机启动
    iterator = 20;
}

void OnKeyDown_3()
    { 
    	if(iterator <= 0)//占空比拉满还加就给置位到0%
    	{
            iterator = 20;//随然到头了我还给初始化，但是我还是得让电机停下来
            DC_Motor = 0; //ULN2003输入0，输出1，电机两电极都是1，不转
        }	
    	else
    		iterator -= 5;//按一次减一次，一次减5%
    }
   
/*
键盘状态扫描
*/     
void scanKey()
{
    u8 curKeyStat_0 = P3_0;
    u8 curKeyStat_1 = P3_1;
    u8 curKeyStat_2 = P3_2;
    u8 curKeyStat_3 = P3_3;

    switch(curKeyStat_0)
    {
        case KEYDOWN:
            DelayMs(10);
            if(curKeyStat_0 == KEYDOWN)
            {
                OnKeyDown_0();
            }
            break;
    }
    switch(curKeyStat_1)
    {
        case KEYDOWN:
            DelayMs(10);
            if(curKeyStat_1 == KEYDOWN)
            {
                OnKeyDown_1();
            }
            break;
    }
    switch(curKeyStat_2)
    {
        case KEYDOWN:
            DelayMs(10);
            if(curKeyStat_2 == KEYDOWN)
            {
                OnKeyDown_2();
            }
            break;
    }
    switch(curKeyStat_3)
    {
        case KEYDOWN:
            DelayMs(10);
            if(curKeyStat_3 == KEYDOWN)
            {
                OnKeyDown_3();
            }
            break;
    }
  }
/*
液晶显示初始化、写命令、写数据
*/

void lcdComd(u8 comd)
{
    lcdRS = 0;//指令
    lcdRW = 0;//写
    P0 = comd;
    DelayMs(1);
    lcdEN = 1;
    lcdEN = 0;//使能电平变化，才会把P0送入lcd的D0~D7数据口
}
void lcdData(u8 dat)
{
    lcdRS = 1;
    lcdRW = 0;
    P0 = dat;
    DelayMs(1);
    lcdEN = 1;
    lcdEN = 0;
}
void lcdInit()
{
    lcdComd(0x38);//8位数据，双列，5*7字形
    lcdComd(0x0c);//开启显示屏，关光标，光标不闪
    lcdComd(0x06);//显示地址递增，显示一位后自动右移一位
    lcdComd(0x01);//清屏
}


/*
显示程序：
*/
void disPlay(u8 *table,u8  *str)
{
    
    u8 index;
    lcdInit();//lcdInit();在这里初始化，显示会一直清屏，一闪一闪的
    lcdComd(0x80);
    for(index = 0; index < 11; index ++)//sizeof()后面有个空的位置
    {
        lcdData(* table ++);
    }
    
    DelayMs(10);
    
    lcdComd(0x80 + 0x40 + 0x06);

    for(index = 0; index < 3; index ++)//sizeof()后面有个空的位置
    {
        lcdData(str[index]);
        //str ++;
    }
    
    lcdData(0x25);
}

void main()
{  
    SetTH(0,0x06);//250us，不知道行不行
    SetTL(0,0x06);//
    MakeByte(TMOD,0x02,0x02);
    /*
      配置定时计数器工作方式，定时器0和1都设置为：8位自动重装
      TMOD |= ((0000 0010<<4 = 0010 0000) | 0000 0010) ——>TMOD |= 0010 0010;	
    */
    EnableET(0);//定时器0允许，中断寄存器允许（开使能）
    TR0 = 0x01; //打开定时器0（运行）
     
    iterator = 20;//迭代器初值

    DC_Motor = 1;//ULN2003，输入端为1，输出就为0,但其实开发板有上拉电阻，上电就转了，都不用控制
    while(1)
    {
        scanKey();
        percentage = iterator * 10 / copies;
        sprintf(&string, "%d",percentage);//iterator是十进制，直接显示是乱码，得先转为字符串
        disPlay(&table,&string);
        DelayMs(100);
    }
}

/*
cnt为中断执行次数，设为100好对半分，总的时间为cnt*中断定时时间
condition 为50时，占空比为50%，改变condition即可改变占空比
假设10us执行一次中断，100us*100 = 10ms,就是前5毫秒高电平，后5毫秒低电平
*/
void timer() interrupt 1
{
    static u16 cnt;
    
    TR0 = 0x00;
  
    cnt ++;
    if(cnt == copies)
    {
        cnt = 0;
    }

    if(cnt < iterator)
    {
        P1 = 0x0F;//ULN2003输入1，输出0，就能转
    }
    else
    {
        P1 = 0x00;//输入0，输出1，电机两端都是1，就不能转
    }
    TR0 = 0x01;
}